﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Threading;
using DevEnverModal;
using Quail;
using QuailDashboard.View.Controls;
using QuailDashboard.View.Converters;
using QuailDashboard.View.Data;
using QuailDashboard.View.Helpers;
using QuailDashboard.View.QuailServiceReference;
using QuailDashboard.View.QuailTestRunnerServiceReference;
using QuailDashboard.View.Views;

namespace QuailDashboard.View.Models
{
    /// <summary>
    /// The state of the home page
    /// </summary>
    public class HomePageState : IsProcessingStateModel
    {
        private readonly QuailDataServiceClient _service;
        private readonly QuailTestRunnerClient _testRunnerClient;
        private readonly TestClassHeirarchyList _testClassHeirarchyList = new TestClassHeirarchyList(null);
        private readonly Dictionary<string, RunStatusResultWrapper> _testResultWrapperDictionary = new Dictionary<string, RunStatusResultWrapper>();
        private readonly ObservableCollection<RunStatusResultWrapper> _tests = new ObservableCollection<RunStatusResultWrapper>();
        private readonly Dictionary<string, TestRunDataStaleIndicator> _testdictionary = new Dictionary<string, TestRunDataStaleIndicator>();
        private readonly IEnumerable<RunStatusResultWrapper> _filteredTests;
        private readonly FilterModel _filterModel = new FilterModel();
        private readonly Dictionary<string, IEnumerable<RunStatusResultWrapper>> _executingTests = new Dictionary<string, IEnumerable<RunStatusResultWrapper>>();
        private TestEnvironment _selectedTestEnvironment;
        private TestClassHeirarchy _selectedTestClass;
        private RunStatusResultWrapper _selectedTest, _selectedHistoryTest;
        private TestNote _selectedTestNote;
        private bool _layoutLoaded;
        private string _username;
        private DateTime? _filterDate;
        private DispatcherTimer _testExectionTimer;
        private int _executingTestsCounter;
        


        /// <summary>
        /// Initializes a new instance of the <see cref="HomePageState"/> class.
        /// </summary>
        public HomePageState()
        {
            _testRunnerClient = QuailTestRunnerFactory.CreateClient();
            _testRunnerClient.GetBinaryPackagesForAssembliesCompleted += ExecuteTestsGotPackages;
            _testRunnerClient.RunTestPackageLibraryCompleted += RunTestPackageLibraryCompleted;
            _testRunnerClient.IsTestRunningCompleted += OnIsTestRunningCompleted;
            _testRunnerClient.TestRunResultsCompleted += OnTestRunResultsCompleted;

            _service = QuailDataServiceFactory.CreateClient();
            _service.GetRunStatusCompleted += OnGetTestRunStatusCompleted;
            _service.GetRunStatusForDateCompleted += OnGetTestRunStatusForDateCompleted;
            _service.GetRunStatusForForTestAndDateCompleted += OnGetRunStatusForTestAndDateCompleted;
            _service.AddTestNoteCompleted += OnTestNoteSomethingCompleted;
            _service.UpdateTestNoteCompleted += OnTestNoteSomethingCompleted;
            _service.ActivateTestNoteCompleted += OnTestNoteSomethingCompleted;
            _service.GetTestNotesCompleted += OnGetTestNotesCompleted;
            _service.GetCurrentUserCompleted += OnGetCurrentUserCompleted;
            _service.GetCurrentUserAsync();
            _service.GetEnvironmentCompleted += OnGetEnvironmentCompleted;
            _service.GetRunStatusForTestsAndDateCompleted += OnGetRunStatusForTestsAndDateCompleted;
            _filterModel.PropertyChanged += OnFilterModelPropertyChanged;
            _filterModel.FilterChanged += OnFilterModelChanged;

            _filteredTests = _tests.Where(data =>
            {
                var statusid = (LogStatus)data.RunStatusId;

                if (!string.IsNullOrEmpty(FilterString))
                {
                    if (data.TestName.IndexOf(FilterString, StringComparison.CurrentCultureIgnoreCase) < 0)
                    {
                        if (data.TestOwner == null)
                            return false;

                        if (data.TestOwner.IndexOf(FilterString, StringComparison.CurrentCultureIgnoreCase) < 0)
                            return false;
                    }
                }

                if (!FilterFailed && 
                    !FilterFailedYSOD && 
                    !FilterFailedTimeout &&
                    !FilterFailedQuail &&
                    !FilterFailedSync &&
                    !FilterFailedThirdParty &&
                    !FilterAsyncPending &&
                    !FilterPending && 
                    !FilterWarning && 
                    !FilterPassed)
                    return true;

                if (FilterFailed && statusid == LogStatus.Fail)
                    return true;

                if (FilterFailedYSOD && statusid == LogStatus.FailYsod)
                    return true;

                if (FilterFailedTimeout && statusid == LogStatus.FailTimeout)
                    return true;

                if (FilterFailedQuail && statusid == LogStatus.FailQuail)
                    return true;
                
                if (FilterFailedSync && statusid == LogStatus.FailSync)
                    return true;

                if (FilterFailedThirdParty && statusid == LogStatus.FailThirdParty)
                    return true;

                if (FilterPending && statusid == LogStatus.Pending)
                    return true;

                if (FilterAsyncPending && statusid == LogStatus.AsyncPending)
                    return true;

                if (FilterPassed && statusid == LogStatus.Pass)
                    return true;

                if (!FilterWarning)
                    return false;

                // Warning state is a "catch all" for all other states
                switch (statusid)
                {
                    case LogStatus.Pass:
                    case LogStatus.AsyncPending:
                    case LogStatus.Pending:
                    case LogStatus.Fail:
                    case LogStatus.FailYsod:
                    case LogStatus.FailTimeout:
                    case LogStatus.FailThirdParty:
                    case LogStatus.FailQuail:
                    case LogStatus.FailSync:
                        return false;
                    default:
                        return true;
                }
            });
        }

        /// <summary>
        /// Delegate for when the selected test class changes
        /// </summary>
        /// <param name="sender">This object</param>
        /// <param name="oldtest">the previously selected test class</param>
        /// <param name="newtest">the newly selected test class</param>
        public delegate void OnSelectedTestClassChanged(object sender, TestClassHeirarchy oldtest, TestClassHeirarchy newtest);

        /// <summary>
        /// Delegate called when the test environment changes
        /// </summary>
        /// <param name="sender">this object</param>
        /// <param name="oldenv">the previously selected test environment</param>
        /// <param name="newenv">the newly selected test environment</param>
        public delegate void OnSelectedTestEnvironmentChanged(object sender, TestEnvironment oldenv, TestEnvironment newenv);

        /// <summary>
        /// Delegate called when the selected test changes
        /// </summary>
        /// <param name="sender">this object</param>
        /// <param name="oldtest">the previously selected test</param>
        /// <param name="newtest">the newly selected test</param>
        public delegate void OnSelectedTestChanged(object sender, RunStatusResultWrapper oldtest, RunStatusResultWrapper newtest);

        /// <summary>
        /// Delegate called when an async exception occurs
        /// </summary>
        /// <param name="sender">this object</param>
        /// <param name="e">the exception</param>
        public delegate void OnHandleError(object sender, Exception e);

        /// <summary>
        /// Fired when a filter-related item changes
        /// </summary>
        public event EventHandler FilterChanged;

        /// <summary>
        /// Fired when the selected test class changes
        /// </summary>
        public event OnSelectedTestClassChanged SelectedTestClassChanged;

        /// <summary>
        /// Fired when the selected test environment is changed
        /// </summary>
        public event OnSelectedTestEnvironmentChanged SelectedTestEnvironmentChanged;

        /// <summary>
        /// Fired when the Tests are loaded from an Async call
        /// </summary>
        public event EventHandler TestsLoaded;

        /// <summary>
        /// Called when the selected test changed
        /// </summary>
        public event OnSelectedTestChanged SelectedTestChanged;

        /// <summary>
        /// Called when an async exception occurs
        /// </summary>
        public event OnHandleError HandleError;

        /// <summary>
        /// Gets or sets the ModalControl manager for the home page
        /// </summary>
        public ModalControl Modal
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets a value indicating whether the page Is loaded
        /// </summary>
        public bool IsLayoutLoaded
        {
            get
            {
                return _layoutLoaded;
            }

            set
            {
                _layoutLoaded = value;
                NotifyPropertyChanged("IsLayoutLoaded");
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to filter failed records
        /// </summary>
        public bool FilterFailed
        {
            get
            {
                return _filterModel.FilterFailed;
            }

            set
            {
                _filterModel.FilterFailed = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to filter YSOD records
        /// </summary>
        public bool FilterFailedTimeout
        {
            get
            {
                return _filterModel.FilterFailedTimeout;
            }

            set
            {
                _filterModel.FilterFailedTimeout = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to filter quail failure records
        /// </summary>
        public bool FilterFailedQuail
        {
            get
            {
                return _filterModel.FilterFailedQuail;
            }

            set
            {
                _filterModel.FilterFailedQuail = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to filter failed third party records
        /// </summary>
        public bool FilterFailedThirdParty
        {
            get
            {
                return _filterModel.FilterFailedThirdParty;
            }

            set
            {
                _filterModel.FilterFailedThirdParty = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to filter Sync failure records
        /// </summary>
        public bool FilterFailedSync
        {
            get
            {
                return _filterModel.FilterFailedSync;
            }

            set
            {
                _filterModel.FilterFailedSync = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to filter YSOD records
        /// </summary>
        public bool FilterFailedYSOD
        {
            get
            {
                return _filterModel.FilterFailedYSOD;
            }

            set
            {
                _filterModel.FilterFailedYSOD = value;
            }
        }
        /// <summary>
        /// Gets or sets a value indicating whether to filter passed records
        /// </summary>
        public bool FilterPassed
        {
            get
            {
                return _filterModel.FilterPassed;
            }

            set
            {
                _filterModel.FilterPassed = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to filter warning records
        /// </summary>
        public bool FilterWarning
        {
            get
            {
                return _filterModel.FilterWarning;
            }

            set
            {
                _filterModel.FilterWarning = value;
            }
        }

        public bool FilterAsyncPending
        {
            get { return _filterModel.FilterAsyncPending; }
            set { _filterModel.FilterAsyncPending = value; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to filter pending records
        /// </summary>
        public bool FilterPending
        {
            get
            {
                return _filterModel.FilterPending;
            }

            set
            {
                _filterModel.FilterPending = value;
            }
        }

        /// <summary>
        /// Gets or sets the filter string
        /// </summary>
        public string FilterString
        {
            get
            {
                return _filterModel.FilterString;
            }

            set
            {
                _filterModel.FilterString = value;
            }
        }

        /// <summary>
        /// Gets the currently selected test class name
        /// </summary>
        public string SelectedTestClassName
        {
            get
            {
                var selectedclass = SelectedTestClass;
                if (selectedclass == null)
                    return null;

                return selectedclass.FullClassName;
            }
        }

        /// <summary>
        /// Gets FilteredTestCount.
        /// </summary>
        public int FilteredTestCount
        {
            get
            {
                var filteredtests = FilteredTestsForSelectedTestClass.ToList();
                return filteredtests.Count;
            }
        }

        /// <summary>
        /// Gets or sets the filter date
        /// </summary>
        public DateTime? FilterDate
        {
            get
            {
                return _filterDate;
            }

            set
            {
                if (value.HasValue)
                    _filterDate = value.Value.Date;
                else
                    _filterDate = null;

                NotifyPropertyChanged("FilterDate");
                RefreshTests();
            }
        }

        /// <summary>
        /// Gets the current help page to use
        /// </summary>
        public Uri HelpPage
        {
            get
            {
                if (_selectedTest != null)
                    return TestHelpUrl.GetTestHelpUrl(_selectedTest.FullName, TestHelpUrl.TestHelpUrlTypes.Test);

                return null;
            }
        }

        public Uri LoadTestsPage
        {
            get
            {
                return new Uri("/LoadTest.aspx", UriKind.Relative);
            }
        }

        public Uri TestChartsPage
        {
            get
            {
                var uri = "/Chart.aspx?env=" + HttpUtility.UrlEncode(SelectedTestEnvironment.EnvironmentName);

                if (SelectedTest != null && ActiveSelection == ActiveSelectionEnum.Test)
                    uri += "&test=" + HttpUtility.UrlEncode(SelectedTest.FullName);
                else if (SelectedTestClass != null)
                    uri += "&class=" + HttpUtility.UrlEncode(SelectedTestClass.FullClassName);

                return new Uri(uri, UriKind.Relative);
            }
        }

        /// <summary>
        /// Gets the currently selected test's TestRunId
        /// </summary>
        public int SelectedTestRunId
        {
            get
            {
                return _selectedTest == null ? 0 : _selectedTest.TestRunId;
            }
        }

        /// <summary>
        /// Gets the currently selected history test's TestRunId
        /// </summary>
        public int SelectedHistoryTestRunId
        {
            get
            {
                return _selectedHistoryTest == null ? 0 : _selectedHistoryTest.TestRunId;
            }
        }

        public string SelectedEnvironmentDescription
        {
            get
            {
                if (_selectedTestEnvironment != null)
                    return _selectedTestEnvironment.Description;

                return null;
            }
        }

        /// <summary>
        /// Gets or sets the currently selected test environment
        /// </summary>
        public TestEnvironment SelectedTestEnvironment
        {
            get
            {
                return _selectedTestEnvironment;
            }

            set
            {
                var old = _selectedTestEnvironment;
                _selectedTestEnvironment = value;
                NotifyPropertyChanged("SelectedTestEnvironment");
                NotifyPropertyChanged("SelectedEnvironmentDescription");
                if (SelectedTestEnvironmentChanged != null)
                    SelectedTestEnvironmentChanged(this, old, value);

                RefreshTests();
            }
        }

        /// <summary>
        /// Gets or sets the selected test
        /// </summary>
        public RunStatusResultWrapper SelectedTest
        {
            get
            {
                return _selectedTest;
            }

            set
            {
                var oldpage = TestChartsPage.ToString();
                var old = _selectedTest;
                _selectedTest = value;
                NotifyPropertyChanged("SelectedTest");
                NotifyPropertyChanged("SelectedTestRunId");
                NotifyPropertyChanged("HelpPage");

                if (oldpage != TestChartsPage.ToString())
                    NotifyPropertyChanged("TestChartsPage");

                if (SelectedTestChanged != null)
                    SelectedTestChanged(this, old, value);
            }
        }

        /// <summary>
        /// Gets or sets the selected history test
        /// </summary>
        public RunStatusResultWrapper SelectedHistoryTest
        {
            get
            {
                return _selectedHistoryTest;
            }

            set
            {
                _selectedHistoryTest = value;
                NotifyPropertyChanged("SelectedHistoryTest");
                NotifyPropertyChanged("SelectedHistoryTestRunId");
            }
        }

        /// <summary>
        /// Gets the selected testnote id
        /// </summary>
        public int SelectedTestNoteId
        {
            get
            {
                return _selectedTestNote == null ? 0 : _selectedTestNote.TestNoteId;
            }
        }

        /// <summary>
        /// Gets or sets the selected testnote
        /// </summary>
        public TestNote SelectedTestNote
        {
            get
            {
                return _selectedTestNote;
            }

            set
            {
                _selectedTestNote = value;
                NotifyPropertyChanged("SelectedTestNote");
                NotifyPropertyChanged("SelectedTestNoteId");
            }
        }

        public enum ActiveSelectionEnum
        {
            TestClass,
            Test
        }

        private ActiveSelectionEnum _activeSelection;
        public ActiveSelectionEnum ActiveSelection
        {
            get { return _activeSelection; }


            set
            {
                var oldpage = TestChartsPage.ToString();
                _activeSelection = value;
                if (oldpage != TestChartsPage.ToString())
                    NotifyPropertyChanged("TestChartsPage");
            }
        }

        /// <summary>
        /// Gets or sets the currently selected test class
        /// </summary>
        public TestClassHeirarchy SelectedTestClass
        {
            get
            {
                return _selectedTestClass;
            }

            set
            {
                var oldpage = TestChartsPage.ToString();
                var old = _selectedTestClass;
                _selectedTestClass = value;
                NotifyPropertyChanged("SelectedTestClass");
                NotifyPropertyChanged("SelectedTestClassName");
                NotifyPropertyChanged("FilteredTestCount");
                if (oldpage != TestChartsPage.ToString())
                    NotifyPropertyChanged("TestChartsPage");
                

                if (SelectedTestClassChanged != null)
                    SelectedTestClassChanged(this, old, value);
            }
        }

        /// <summary>
        /// Gets a list of all the tests
        /// </summary>
        public ObservableCollection<RunStatusResultWrapper> Tests
        {
            get
            {
                return _tests;
            }
        }

        /// <summary>
        /// Gets the heirarchal list of test classes
        /// </summary>
        public TestClassHeirarchyList TestClassHeirarchyList
        {
            get
            {
                return _testClassHeirarchyList;
            }
        }

        /// <summary>
        /// Gets an enumerable list of all the tests matching the current filter
        /// </summary>
        public IEnumerable<RunStatusResultWrapper> FilteredTests
        {
            get
            {
                return _filteredTests;
            }
        }

        /// <summary>
        /// Gets an enumerable list of all the tests matching the current filter and selected test class
        /// </summary>
        /// <returns>The list of filtered tests</returns>
        public IEnumerable<RunStatusResultWrapper> FilteredTestsForSelectedTestClass
        {
            get
            {
                return FilteredTests.Where(test => SelectedTestClassName == null || test.CompleteTestClassName.StartsWith(SelectedTestClassName));
            }
        }

        /// <summary>
        /// Gets or sets the user's name
        /// </summary>
        public string UserName
        {
            get
            {
                return _username;   
            }

            set
            {
                _username = value;
                NotifyPropertyChanged("UserName");
            }
        }

        public void LoadEnvironment(string environmentname)
        {
            _service.GetEnvironmentAsync(environmentname);
            IncProcessing("Getting environment...");
        }

        /// <summary>
        /// Refreshes the tests
        /// </summary>
        public void RefreshTests()
        {
            if (SelectedTestEnvironment != null)
            {
                _service.GetRunStatusForForTestAndDateAsync(SelectedTestEnvironment.TestEnvironmentId, null, FilterDate, _tests);
                IncProcessing("Refreshing data...");
            }
        }

        /// <summary>
        /// Refresh the list of test notes
        /// </summary>
        /// <param name="test">the test data</param>
        public void RefreshTestNotes(RunStatusResultWrapper test)
        {
            _service.GetTestNotesAsync(test.TestId, true, test);
            IncProcessing("Getting notes...");
        }

        /// <summary>
        /// Adds a new testnote to the database and the collection
        /// </summary>
        /// <param name="test">the test data</param>
        /// <param name="newnote">the new testnote</param>
        public void AddTestNote(RunStatusResultWrapper test, TestNote newnote)
        {
            _service.AddTestNoteAsync(newnote.TestId, newnote.CreatedBy, newnote.Note, test);
            IncProcessing("Adding new note...");
        }

        /// <summary>
        /// Saves a testnote
        /// </summary>
        /// <param name="test">the test data</param>
        /// <param name="oldnote">the testnote</param>
        public void SaveTestNote(RunStatusResultWrapper test, TestNote oldnote)
        {
            _service.UpdateTestNoteAsync(oldnote, test);
            IncProcessing("Saving note...");
        }

        /// <summary>
        /// Deactivates a testnote
        /// </summary>
        /// <param name="test">the test data</param>
        /// <param name="oldnote">the testnote to remove</param>
        public void DeleteTestNote(RunStatusResultWrapper test, TestNote oldnote)
        {
            _service.ActivateTestNoteAsync(oldnote.TestNoteId, false, test);
            IncProcessing("Deactivating note...");
        }

        /// <summary>
        /// Gets a list of tests for the history of the selected test class
        /// </summary>
        /// <returns>the list of tests (asynchronously filled)</returns>
        public ObservableCollection<RunStatusResultWrapper> GetHistoryTestsForSelectedTest()
        {
            var result = new ObservableCollection<RunStatusResultWrapper>();

            if (SelectedTest != null)
            {
                var service = QuailDataServiceFactory.CreateClient();
                service.GetRunStatusCompleted += OnGetHistoryCompleted;
                service.GetRunStatusAsync(SelectedTest.TestEnvironmentId, SelectedTest.FullName, result);
                IncProcessing("Getting history...");
            }

            return result;
        }

        /// <summary>
        /// Attempts to take ownership of the test
        /// </summary>
        /// <param name="test">the test to own</param>
        public void StartTakeOwnership(RunStatusResultWrapper test)
        {
            var service = QuailDataServiceFactory.CreateClient();
            service.SetTestOwnerCompleted += OnSetTestOwnerCompleted;
            service.SetTestOwnerAsync(test.TestEnvironmentId, test.TestId, UserName, false, test);
            IncProcessing("Please wait...");
        }

        /// <summary>
        /// Attempts to clear ownership of the test
        /// </summary>
        /// <param name="test">the test to clear</param>
        public void StartClearOwnership(RunStatusResultWrapper test)
        {
            var service = QuailDataServiceFactory.CreateClient();
            service.ClearTestOwnerCompleted += OnClearTestOwnerCompleted;
            service.ClearTestOwnerAsync(test.TestEnvironmentId, test.TestId, test.TestOwner, false, test);
            IncProcessing("Please wait...");
        }

        private IEnumerable<RunStatusResultWrapper> GetTests(IEnumerable<RunStatusResultWrapper> tests, IEnumerable<TestClassHeirarchy> testclasses)
        {
            if (tests != null)
            {
                foreach (var t in tests)
                    yield return t;
            }

            if (testclasses != null)
            {
                foreach (var t in testclasses.SelectMany(tc => tc.GetAllTests()))
                    yield return t;
            }            
        }

        /// <summary>
        /// Execute Tests
        /// </summary>
        public void ExecuteTests(IEnumerable<RunStatusResultWrapper> tests, IEnumerable<TestClassHeirarchy> testclasses)
        {
            // var oc = new ObservableCollection<string>();
            // testNames.ForEach(oc.Add);

            IncProcessing("Gathering test information");


            var options = new TestRunOptions
                              {
                                  DefaultEnvironment = SelectedTestEnvironment.EnvironmentName,
                                  Tests = GetTests(tests, testclasses)
                              };

            if (testclasses != null)
            {
                // get all test classe names
                options.TestClasses = testclasses.SelectMany(t => t.GetLeafClassNames()).ToObservableCollection();
            }

            if (tests != null)
            {
                // add the test names to the options
                options.TestMethods = tests.Select(t => t.FullName).ToObservableCollection();
            }

            // get the name of the assembly file for each test, add a unique list to options.TestAssemblies
            options.TestAssemblies = options.Tests.Select(t => t.LibraryName.ToLower() + ".dll").Distinct().ToObservableCollection();

            //options.CustomOverrides = new Dictionary<string, string>();
            //options.CustomOverrides.Add("NumberOfTests", );
            //options.CustomOverrides.Add("NumberOfThreads", );


            _testRunnerClient.GetBinaryPackagesForAssembliesAsync(options.TestAssemblies, options);
        }

        private void ExecuteTestsGotPackages(object sender, GetBinaryPackagesForAssembliesCompletedEventArgs e)
        {
            DecProcessing();

            if (e.Error != null)
            {
                App.CurrentApp.ShowError("Error getting Test Packages", e.Error);
                return;
            }

            var packages = e.Result;

            if (packages.Count == 0)
            {
                ChildWindow errorWin = new ErrorWindow("No Packages", "Unable to find a package for the selected test(s)");
                errorWin.Show();
                return;
            }

            var options = (TestRunOptions)e.UserState;


            if (packages.Count == 1)
            {
                RunTests(packages[0], options);
            }
            else
            {
                var ctrl = new SelectBinaryPackageControl { Modal = Modal, Options = options, Packages = packages };
                ctrl.Select += OnSelectBinaryPackage;
                Modal.ShowModal(ctrl);
            }
        }

        private void OnSelectBinaryPackage(object sender, EventArgs e)
        {
            var ctrl = (SelectBinaryPackageControl) sender;
            RunTests(ctrl.SelectedBinaryPackage, ctrl.Options);
        }

        private void RunTests(BinaryPackageInfo package, TestRunOptions options)
        {
            IncProcessing("Starting tests...");
            
            _testRunnerClient.RunTestPackageLibraryAsync(package.PackageName, package.PackageVersion, options, options.Tests);            
        }

        private void RunTestPackageLibraryCompleted(object sender, RunTestPackageLibraryCompletedEventArgs e)
        {
            DecProcessing();

            if (e.Error != null)
            {
                App.CurrentApp.ShowError(e.Error);
            }
            else
            {
                // for all the tests, refresh them?
                var tests = e.UserState as IEnumerable<RunStatusResultWrapper>;

                if (tests != null)
                {
                    tests.ForEach(t =>
                                      {
                                          if (!t.IsExecuting)
                                          {
                                              t.IsExecuting = true;
                                              t.IsExecutingNewTest = false;
                                          }

                                          t.IsChecked = false;
                                      });

                    _executingTests[e.Result] = tests;

                    if (_testExectionTimer == null)
                    {
                        _testExectionTimer = new DispatcherTimer {Interval = new TimeSpan(0, 0, 0, 15)};
                        _testExectionTimer.Tick += OnTestExecutionTimerTick;
                        _testExectionTimer.Start();
                    }
                }
            }
        }

        private void OnTestExecutionTimerTick(object sender, EventArgs e)
        {
            if (_executingTests.Keys.Count == 0)
            {
                _testExectionTimer.Stop();
                _testExectionTimer = null;
            }
            else
            {
                if (_executingTestsCounter == 0)
                {
                    _executingTestsCounter = _executingTests.Keys.Count;
                    _executingTests.Keys.ForEach(testrunid => _testRunnerClient.IsTestRunningAsync(testrunid, testrunid));
                }
            }
        }

        private void OnTestRunResultsCompleted(object sender, TestRunResultsCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                App.CurrentApp.ShowError("Error getting Test Run Results", e.Error);
            }
        }

        private void OnIsTestRunningCompleted(object sender, IsTestRunningCompletedEventArgs e)
        {
            _executingTestsCounter--;

            if (e.Error != null)
            {
                App.CurrentApp.ShowError("Error getting IsTestRunning status", e.Error);
            }
            else
            {
                var testrunid = (string) e.UserState;
                IEnumerable<RunStatusResultWrapper> tests;
                if (_executingTests.TryGetValue(testrunid, out tests))
                {
                    var edict = new Dictionary<int, List<RunStatusResultWrapper>>();
                    tests.ForEach(test =>
                                      {
                                          List<RunStatusResultWrapper> list;

                                          if (!edict.TryGetValue(test.TestEnvironmentId, out list))
                                          {
                                              list = new List<RunStatusResultWrapper>();
                                              edict.Add(test.TestEnvironmentId, list);
                                          }

                                          list.Add(test);
                                      });

                    foreach (var envid in edict.Keys)
                    {
                        var list = edict[envid];
                        _service.GetRunStatusForTestsAndDateAsync(envid, list.Select(t => t.FullName).ToObservableCollection(), null, list);
                    }

                    if (!e.Result)
                    {
                        _testRunnerClient.TestRunResultsAsync(testrunid);
                        _executingTests.Remove(testrunid);

                        // disable the executing flag, just in case it was forgotten
                        tests.ForEach(test => test.IsExecuting = false);
                    }
                }
            }
        }

        private void OnClearTestOwnerCompleted(object sender, ClearTestOwnerCompletedEventArgs e)
        {
            DecProcessing();
            if (e.Error != null)
                HandleException(e.Error);
            else
            {
                var test = (RunStatusResultWrapper)e.UserState;
                test.TestOwner = e.owner;
            }
        }

        private void OnSetTestOwnerCompleted(object sender, SetTestOwnerCompletedEventArgs e)
        {
            DecProcessing();

            if (e.Error != null)
                HandleException(e.Error);
            else
            {
                var test = (RunStatusResultWrapper)e.UserState;
                test.TestOwner = e.owner;
            }
        }

        private void OnGetTestNotesCompleted(object sender, GetTestNotesCompletedEventArgs e)
        {
            DecProcessing();
            var test = (RunStatusResultWrapper)e.UserState;
            var notes = test.TestNotes;

            notes.Clear();

            if (e.Error != null)
                HandleException(e.Error);
            else
            {
                var reg = new Regex(@"<(.|\n)*?>");
                var regnlstart = new Regex(@"^(\r\n)+");
                var regnlend = new Regex(@"(\r\n)+$");

                e.Result.ForEach(n =>
                                     {
                                         var t = n.Note;
                                         var bi = t.IndexOf("<body");
                                         if (bi >= 0)
                                         {
                                             t = reg.Replace(t.Substring(bi), string.Empty);
                                             t = regnlstart.Replace(t, string.Empty);
                                             t = regnlend.Replace(t, string.Empty);

                                             n.Note = t;
                                         }

                                         notes.Add(n);
                                     });
            }

            test.HasNotes = notes.Count > 0;
        }


        private void OnTestNoteSomethingCompleted(object sender, AsyncCompletedEventArgs e)
        {
            DecProcessing();

            if (e.Error != null)
                HandleException(e.Error);
            else
                RefreshTestNotes((RunStatusResultWrapper)e.UserState);
        }

        private void OnGetHistoryCompleted(object sender, GetRunStatusCompletedEventArgs e)
        {
            DecProcessing();

            if (e.Error != null)
                HandleException(e.Error);
            else
            {
                var tests = (ObservableCollection<RunStatusResultWrapper>)e.UserState;
                e.Result.ForEach(test => tests.Add(new RunStatusResultWrapper(test)));
            }
        }

        private void BuildClassHeirarchyList()
        {
            _testClassHeirarchyList.SetTestsStale();
            _tests.ForEach(_testClassHeirarchyList.AddTestClass);
            _testClassHeirarchyList.RemoveTestClassesWithoutTests();
        }

        /// <summary>
        /// Called when the GetTestRunStatusAsync call completes
        /// </summary>
        /// <param name="sender">don't care</param>
        /// <param name="e">don't use it</param>
        private void OnGetTestRunStatusCompleted(object sender, GetRunStatusCompletedEventArgs e)
        {
            ProcessResults(e.Error, e.Result);
        }

        private void OnGetTestRunStatusForDateCompleted(object sender, GetRunStatusForDateCompletedEventArgs e)
        {
            ProcessResults(e.Error, e.Result);
        }

        /// <summary>
        /// Fired when running multiple tests, the test status and IsExecuting state is updated
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void OnGetRunStatusForTestsAndDateCompleted(object sender, GetRunStatusForTestsAndDateCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                App.CurrentApp.ShowError("Error getting test results for running tests.", e.Error);
                return;
            }

            var list = e.UserState as IEnumerable<RunStatusResultWrapper>;

            if (list == null)
                return;

            foreach (var result in e.Result)
            {
                var localtest = list.Where(t => t.FullName == result.FullName).FirstOrDefault();

                if (localtest == null)
                    continue;

                localtest.SetOriginalResultFromDb(result);
            }
        }

        private void OnGetRunStatusForTestAndDateCompleted(object sender, GetRunStatusForForTestAndDateCompletedEventArgs e)
        {
            ProcessResults(e.Error, e.Result);
        }

        /// <summary>
        /// Processes the current test results 
        /// </summary>
        /// <param name="e"></param>
        /// <param name="results">the list of results</param>
        private void ProcessResults(Exception e, IEnumerable<GetRunStatusResult> results)
        {
            DecProcessing();

            if (e != null)
            {
                HandleException(e);
                _tests.Clear();
            }
            else
            {
                if (results == null)
                    _tests.Clear(); // not expected to be null, but just in case
                else
                {
                    // set all the current tests to be stale
                    _testdictionary.ForEach(test => test.Value.IsStale = true);

                    // for each test in the result set,
                    // add it into the system
                    foreach (var test in results)
                    {
                        // generate the test key
                        var key = test.TestEnvironmentId + "." + test.FullName;

                        // see if it's already created
                        RunStatusResultWrapper rsr;
                        if (!_testResultWrapperDictionary.TryGetValue(key, out rsr))
                        {
                            rsr = new RunStatusResultWrapper(test);
                            _testResultWrapperDictionary[key] = rsr;
                        }

                        // set the new value for the test
                        TestRunDataStaleIndicator tsi;

                        if (!_testdictionary.TryGetValue(rsr.FullName, out tsi))
                        {
                            _tests.Add(rsr);
                            _testdictionary.Add(rsr.FullName, new TestRunDataStaleIndicator(rsr));
                        }
                        else
                        {
                            tsi.Data.SetOriginalResultFromDb(test);
                            tsi.IsStale = false;
                        }
                    }

                    var keys = new List<string>();

                    // find all the stale tests
                    _testdictionary
                        .Where(kvp => kvp.Value.IsStale)
                        .ForEach(kvp =>
                                     {
                                         _tests.Remove(kvp.Value.Data);
                                         keys.Add(kvp.Key);
                                     });

                    // remove all the stale tests from the dictionary
                    keys.ForEach(key => _testdictionary.Remove(key));
                }
            }

            if (TestsLoaded != null)
                TestsLoaded(this, EventArgs.Empty);

            // rebuild the test class heirarchy
            BuildClassHeirarchyList();

            // notify the
            OnFilterModelChanged(this, null);
        }

        private void OnGetEnvironmentCompleted(object sender, GetEnvironmentCompletedEventArgs e)
        {
            DecProcessing();

            if (e.Error != null)
                HandleException(e.Error);
            else
            {
                SelectedTestEnvironment = e.Result;
            }

        }

        private void OnGetCurrentUserCompleted(object sender, GetCurrentUserCompletedEventArgs e)
        {
            if (e.Error != null)
                HandleException(e.Error);
            else
            {
                UserName = string.IsNullOrEmpty(e.Result)
                               ? App.CurrentApp.UserName
                               : e.Result;
            }
        }

        private void HandleException(Exception e)
        {
            if (HandleError != null)
                HandleError(this, e);
        }

        private void OnFilterModelPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            NotifyPropertyChanged(e.PropertyName);
        }

        private void OnFilterModelChanged(object sender, EventArgs e)
        {
            if (FilterChanged != null)
                FilterChanged(this, EventArgs.Empty);

            NotifyPropertyChanged("FilteredTestCount");
        }

        /// <summary>
        /// stale indicator
        /// </summary>
        private class TestRunDataStaleIndicator
        {
            private readonly RunStatusResultWrapper _data;

            /// <summary>
            /// Initializes a new instance of the <see cref="TestRunDataStaleIndicator"/> class.
            /// </summary>
            /// <param name="data">The test data.</param>
            public TestRunDataStaleIndicator(RunStatusResultWrapper data)
            {
                _data = data;
            }

            /// <summary>
            /// Gets the test data
            /// </summary>
            public RunStatusResultWrapper Data
            {
                get
                {
                    return _data;
                }
            }

            /// <summary>
            /// Gets or sets a value indicating whether IsStale.
            /// </summary>
            public bool IsStale
            {
                get; set;
            }
        }
    }
}
